/*
 * @module loader
 * @author zfkun@msn.com
 */
;(function(Z) {

    var L = Z.Lang,
        C = Z.Config,
        E = Z.Env,
        doc = E.doc,
        head = doc.getElementsByTagName('head')[0] || doc.documentElement,

        STATUS_LOADING = 1,
        STATUS_LOADED = 2,
        STATUS_ERROR = 3,
        STATUS_ATTACHED = 4;
    
    var _scriptOnload = doc.addEventListener ? function(node, callback) {
        node.addEventListener('load', callback, false);
    } : function(node, callback) {
       var oldCallback = node.onreadystatechange;
       node.onreadystatechange = function() {
           var rs = node.readyState;
           if (rs === 'loaded' || rs === 'complete') {
               node.onreadystatechange = null;
               oldCallback && oldCallback();
               callback.call(this);
           }
       };
   };


   Z.extend({

        add : function(modName, fn, info) {
            var self = this,
                mods = self.Env.mods,
                mod,
                o;

            // Z.add(modName, info) => Z.add( { modName: info } )
            if (L.isString(modName) && !info && L.isPlainObject(fn)) {
                o = {};
                o[modName] = fn;
                modName = o;
            }

            // Z.add( { modName: info } )
            if (L.isPlainObject(modName)) {
                L.each(modName, function(v, k) {
                    v.name = k;
                    if (mods[k]) Z.mix(v, mods[k]);
                });
                Z.mix(mods, modName);
            } else { // Z.add(modName[, fn[, info]])
                info = info || {};
                mod = mods[modName] || {};

                Z.mix(mod, { name : modName, fn : fn, status : STATUS_LOADED });

                Z.mix((mods[modName] = mod), info);

                if(_isAttached(mod.requires)) {
                    _attachMod(mod);
                }
            }

            return self;
        },
        
        use : function(modNames, callback, info) {
            modNames = modNames.replace(/\s+/g, '').split(',');
            info = info || {};

            var self = this,
                modName,
                i,
                l = modNames.length,
                done;

            if(_isAttached(modNames)) {
                callback && callback(self);
                return;
            }

            for (i = 0; i < l; i++) {
                modName = modNames[i];
                _attachModByName(modName, function() {
                    if(!done && _isAttached(modNames)) {
                        done = true;
                        callback && callback(self);
                    }
                });
            }

            return self;
        },

        getScript : function(url, options) {
            var ops = options || {},
                isCSS = ops.type === 'css',
                node = doc.createElement(isCSS ? 'link' : 'script'),
                success, error, timeout, timer, charset;

            if (L.isPlainObject(ops)) {
                success = ops.success;
                error = ops.error;
                timeout = ops.timeout;
                charset = ops.charset;
            }

            if (isCSS) {
                node.href = url;
                node.rel = 'stylesheet';
            } else {
                node.src = url;
                node.async = true;
            }
            
            node.type = isCSS ? 'text/css' : 'text/javascript';
            
            if (charset) {
                node.charset = charset;
            }

            if (!isCSS) {
                _scriptOnload(node, function() {
                    if (timer) {
                        clearTimeout(timer);
                        timer = null;
                    }

                    success && success.call(node);

                    if (head && node.parentNode) {
                        head.removeChild(node);
                    }
                });

                if (L.isFunction(error)) {
                    timer = setTimeout(function() {
                        timer = null;
                        error();
                    }, (timeout || C.loadTimeout) * 1000);
                }

                head.insertBefore(node, head.firstChild);
   
            } else {
                head.appendChild(node);
                success && success.call(node);
            }

            return node;
        }

    });


    function _isAttached(modNames) {
        var mods = Z.Env.mods,
            mod,
            i;

        for (i = (modNames = L.toArray(modNames)).length - 1; i >= 0; i--) {
            mod = mods[modNames[i]];
            if(!mod || mod.status != STATUS_ATTACHED) return false;
        }

        return true;
    }

    function _attachModByName(modName, callback) {
        var mods = Z.Env.mods,
            mod = mods[modName];

        if (!mod) {
            Z.log(modName + ' not found in Env.mods.', 'warn');
            
            // TODO build fullpath
            mod = {
                js : modName + '/' + (Z.Config.moduleJsNamer || function(name){ return name + '-m.js' })(modName),
                charset : 'utf-8'
            };

            mods[modName] = mod;
        }

        mod.name = modName;

        if (mod.status === STATUS_ATTACHED) {
            return;
        }

        _attach(mod, callback);
    };


    function _attachMod(mod) {
        if (mod.fn) {
            mod.fn(Z);
            mod.fn = null;
        }

        mod.status = STATUS_ATTACHED;

        Z.log('[attached] ' + mod.name, 'info');
    }


    function _attach(mod, callback) {
        var self = Z,
            mods = self.Env.mods,
            reqs = (mod.requires || []).concat(),
            i,
            l = reqs.length,
            rm,
            attached = false;

        function _fn() {
            if (!attached && _isAttached(mod.requires)) {
                if (mod.status === STATUS_LOADED) {
                    _attachMod(mod);
                }
                if (mod.status === STATUS_ATTACHED) {
                    attached = true;
                    callback && callback(self);
                }
            }
        }

        for (i = 0; i < l; i++) {
            rm = mods[reqs[i]];
            if(!rm || rm.status !== STATUS_ATTACHED) {
                _attachModByName(reqs[i], _fn);
            }
        }

        _load(mod, function() {
            var newReqs = mod.requires || [],
                optimize = [],

                i,
                newReq,
                newReqMod,
                inArr;
            
            for (i = newReqs.length - 1; i >= 0; i--) {
                newReq = newReqs[i];
                newReqMod = mods[newReq],
                inArr = L.inArray(newReq, reqs);

                if (newReqMod && newReqMod.status === STATUS_ATTACHED || inArr) {
                } else {
                    _attachModByName(newReq, _fn);
                }

                if (!inArr) {
                    optimize.push(newReq);
                }
            }

            if (optimize.length > 0) {
                optimize.unshift(mod.name);
                Z.log(optimize + " : better to be used together", "warn");
            }

            _fn();
        });
    }


    function _load(mod, callback) {
        var self = Z,
            jsUrl = mod.js,
            cssUrl = mod.css,
            queue = Z.Env.__LOAD_QUEUE,
            node = queue[jsUrl];

        mod.status = mod.status || 0;

        if (mod.status < STATUS_LOADING && node) {
            mod.status = node.nodeName ? STATUS_LOADING : STATUS_LOADED;
        }

        if (L.isString(cssUrl)) {
            self.getScript(cssUrl, { type : 'css' });
            mod.css = STATUS_LOADED;
        }

        mod.timeline = +new Date();

        if (mod.status < STATUS_LOADING && jsUrl) {
            mod.status = STATUS_LOADING;

            queue[jsUrl] = self.getScript(jsUrl, {
                type : 'js',
                success : function() {
                    mod.timeline = +new Date - mod.timeline;
                    Z.log('[loaded] ' + mod.name + ', use ' + mod.timeline + 'ms', 'info');
                    _success();
                },
                error : function() {
                    mod.timeline = +new Date - mod.timeline;
                    mod.status = STATUS_ERROR;
                    _complete();
                },
                charset : mod.charset
            });

        } else if (mod.status == STATUS_LOADING) {
            _scriptOnload(node, _success);
        } else {
            callback(self);
        }

        function _success() {
            _complete();
            if (mod.status !== STATUS_ERROR) {
                if (mod.status !== STATUS_ATTACHED) mod.status = STATUS_LOADED;
                callback(self);
            }
        }

        function _complete() {
            queue[jsUrl] = STATUS_LOADED;
        }
    }

})(ZF);
